package featuresProcessing;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import lp.polimi.treefinder.utils.RankElement;

import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;
import org.opencv.highgui.Highgui;

import server.Server;
import utils.DBManager;
import utils.ImageUtils;
import utils.Species;

public class MatchingSystem {

	private static final int FEATURE_LENGTH = 128;
	private static final int SHAPE_DESCRIPTOR_LENGTH = 1;
	public static final String DB_SCHEMA_NAME = "tree_finder";
	private static MatchingSystem sharedInstance = new MatchingSystem();
	private Detector fd = new FeaturesDetector(FeatureDetector.SIFT, DescriptorExtractor.SIFT, DescriptorMatcher.FLANNBASED, "config.yml");
	private Detector sd = new ShapeDetector();
	private ArrayList<Species> fetchedSpecies = new ArrayList<Species>();

	private MatchingSystem() {

	}


	public Detector getShapeDetector () {
		return sd;
	}

	public Detector getFeaturesDetector () {
		return fd;
	}

	/**
	 * Batch procedure to extract shape and key points descriptors from input images and put them in a DB entry of table "species" having
	 * an incremental integer as primary key.
	 */
	public void batchAddSpeciesToDB(String namesFileURL, String imagesFolderURL) throws SQLException, IOException {

		File namesFile = new File(namesFileURL);
		if (!namesFile.exists())
			throw new IllegalArgumentException("The choosen file doesn't exist");

		PreparedStatement stmt = DBManager.getConnection().prepareStatement("insert into "+DB_SCHEMA_NAME+".species values(?, ?, ?, ?)");
		DBManager.getConnection().setAutoCommit(false);

		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(namesFile), Charset.forName("unicode")));
		String line = br.readLine();
		int count = 1;
		int id = -1;
		while (line != null) {
			if (line.equals("")) {
				count++;
				line = br.readLine();
				continue;
			}
			File currImageFolder = new File(imagesFolderURL+"/"+String.format("%03d", count));
			if (currImageFolder.exists() && currImageFolder.isDirectory()) {
				File[] images = currImageFolder.listFiles();
//				for (int i = 0; i < Math.min(30,images.length-1); i++) {
				for (int i = 20; i < images.length-1; i++) {
					id++;
					Mat image = Highgui.imread(currImageFolder+"\\"+images[i].getName(), Highgui.CV_LOAD_IMAGE_GRAYSCALE);

					Mat features = fd.getDescriptors(image);
					int count1 = (int) (features.total() * features.channels());
					float[] featuresBytes = new float[count1];
					features.get(0, 0, featuresBytes);
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					ObjectOutputStream oos = new ObjectOutputStream(baos);
					oos.writeObject(featuresBytes);
					stmt.setBytes(2, baos.toByteArray());
					baos.close();
					oos.close();

					Mat shapeDescriptors = sd.getDescriptors(image);
					int count2 = (int) (shapeDescriptors.total() * shapeDescriptors.channels());
					int[] shapeDescriptorsBytes = new int[count2];
					shapeDescriptors.get(0, 0, shapeDescriptorsBytes);
					baos = new ByteArrayOutputStream();
					oos = new ObjectOutputStream(baos);
					oos.writeObject(shapeDescriptorsBytes);
					stmt.setBytes(3, baos.toByteArray());
					baos.close();
					oos.close();

					stmt.setInt(1, id);
					stmt.setString(4, line);

					stmt.addBatch();
				}
			} else {
				throw new IllegalArgumentException("The choosen folder is not a directory or doesn't exist");
			}
			if (Server.getSharedInstance().getFeedbackManager()==null)
				System.out.println(("MYSQL: "+line+" added to batch ("+count+")"));
			else
				Server.getSharedInstance().getFeedbackManager().printMessage("MYSQL: "+line+" added to batch ("+count+")", false);
			count++;
			line = br.readLine();
		}
		br.close();

		if (Server.getSharedInstance().getFeedbackManager()==null)
			System.out.println(("MYSQL: Executing batch..."));
		else
			Server.getSharedInstance().getFeedbackManager().printMessage("MYSQL: Executing batch...", false);

		stmt.executeBatch();

		if (Server.getSharedInstance().getFeedbackManager()==null)
			System.out.println(("MYSQL: Done."));
		else
			Server.getSharedInstance().getFeedbackManager().printMessage("MYSQL: Done.", false);

		DBManager.getConnection().setAutoCommit(true);
	}

	/**
	 * Batch procedure to fill another DB table ("images") containing species names as primary key and a related PNG picture which
	 * is displayed to the user if that species is in the top-k ranking.
	 */
	public void batchAddImagesToDB(String namesFileURL, String imagesFolderURL) throws SQLException, IOException {
		File namesFile = new File(namesFileURL);
		if (!namesFile.exists())
			throw new IllegalArgumentException("The choosen file doesn't exist");

		PreparedStatement stmt = DBManager.getConnection().prepareStatement("insert into "+DB_SCHEMA_NAME+".images values(?, ?)");
		DBManager.getConnection().setAutoCommit(false);

		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(namesFile), Charset.forName("unicode")));
		String line = br.readLine();
		int count = 1;
		while (line != null) {
			if (line.equals("")) {
				count++;
				line = br.readLine();
				continue;
			}
			File image = new File (imagesFolderURL, String.format("%03d", count)+" (1).png");
			FileInputStream fis = new FileInputStream(image);
			byte[] bytes = new byte[fis.available()];
			fis.read(bytes);
			fis.close();
			stmt.setString(1, line);
			stmt.setBytes(2, bytes);
			stmt.addBatch();
			if (Server.getSharedInstance().getFeedbackManager()==null)
				System.out.println(("MYSQL: "+line+" image added to batch ("+count+")"));
			else
				Server.getSharedInstance().getFeedbackManager().printMessage("MYSQL: "+line+" image added to batch ("+count+")", false);
			count++;
			line = br.readLine();
		}
		br.close();

		if (Server.getSharedInstance().getFeedbackManager()==null)
			System.out.println(("MYSQL: Executing batch..."));
		else
			Server.getSharedInstance().getFeedbackManager().printMessage("MYSQL: Executing batch...", false);

		stmt.executeBatch();
		DBManager.getConnection().setAutoCommit(true);
	}



	public ArrayList<Species> getDB () {
		return fetchedSpecies;
	}

	/**
	 * Retrieve all the descriptors from table "species" and use them to train the Shape Detector and the Feature Detector.
	 */
	public void fetchDatabase() throws SQLException, ClassNotFoundException, IOException {

		if (Server.getSharedInstance().getFeedbackManager()==null)
			System.out.println(("Fetching database..."));
		else
			Server.getSharedInstance().getFeedbackManager().printMessage("Fetching database...", false);


		Statement stmt = DBManager.getConnection().createStatement();
		stmt.execute("select id, features, shape_descriptors, name from "+DB_SCHEMA_NAME+".species");
		ResultSet rs = stmt.getResultSet();
		while (rs.next()) {
			Species s = new Species();
			s.setId(rs.getInt(1));

			ByteArrayInputStream bais = new ByteArrayInputStream(rs.getBytes(2));
			ObjectInputStream ois = new ObjectInputStream(bais);
			float[] featuresBytes = (float[])ois.readObject();
			Mat features = new Mat(new Size(FEATURE_LENGTH, featuresBytes.length/FEATURE_LENGTH), 5);
			features.put(0, 0, featuresBytes);
			s.setFeatures(features);
			bais.close();
			ois.close();

			bais = new ByteArrayInputStream(rs.getBytes(3));
			ois = new ObjectInputStream(bais);
			int[] shapeDescriptorsBytes = (int[])ois.readObject();
			Mat shapeDescriptors = new Mat(new Size(1, shapeDescriptorsBytes.length/(2*SHAPE_DESCRIPTOR_LENGTH)), 12);
			shapeDescriptors.put(0, 0, shapeDescriptorsBytes);
			s.setShapeDescriptors(shapeDescriptors);
			bais.close();
			ois.close();

			s.setName(rs.getString(4));

			fetchedSpecies.add(s);
		}

		sd.addTrainingDescriptors(fetchedSpecies);
		fd.addTrainingDescriptors(fetchedSpecies);

	}

	public void manualShapeTraining (List<Species> setOfImages) {
		sd.addTrainingDescriptors(setOfImages);
	}
	public void manualFeatureTraining (List<Species> setOfImages) {
		fd.addTrainingDescriptors(setOfImages);
	}

	
	/**
	 * Perform the global classification of the query leaf: extract key point descriptors and shape descriptors, use them to
	 * get a ranking from each detector and sent the rankings to the aggregator.
	 * @param species The query leaf.
	 * @param rankGen The rank aggregator.
	 * @return The final ranking.
	 */
	public ArrayList<RankElement> classify(Species species, RankGenerator rankGen) {
		try {
			return rankGen.computeRank(fd.classify(species.getFeatures()), sd.classify(species.getShapeDescriptors()), fetchedSpecies);
		} catch (EmptyClassifierException e) {
			e.printStackTrace();
			return null;
		}
	}


	public static MatchingSystem getSharedInstance() {
		return sharedInstance;
	}


}
